
import uuid
from .uploaders import *
from .reflist import *
import database
import pickle
from typing import List,Dict
# # // LocalRepo is a collection of packages created locally
class LocalRepo :
    # // Permanent internal ID
    UUID =''
    # // User-assigned name
    Name =''
    # // Comment
    Comment =''
    # // DefaultDistribution
    DefaultDistribution =''
    # // DefaultComponent
    DefaultComponent =''
    # // Uploaders configuration
    Uploaders =None
    # // "Snapshot" of current list of packages
    packageRefs :PackageRefList=PackageRefList()
    def __init__(self) -> None:
        self.Uploaders=Uploaders ()
    # // String interface
    def  String(self) ->str:
        if self.Comment != "" :
            return "[{}]: {}".format (self.Name, self.Comment)
        return "[{}]".format( self.Name)
    # // NumPackages return number of packages in local repo
    def NumPackages(repo) ->int :
        if repo.packageRefs is None  :
            return 0
        
        return len(repo.packageRefs)


    # // RefList returns package list for repo
    def RefList(repo ) ->PackageRefList :
        return repo.packageRefs


    # // Key is a unique id in DB
    def  Key(repo)->str:
        return "L" + repo.UUID


    # // RefKey is a unique id for package reference list
    def  RefKey(repo) ->str:
        return "E" + repo.UUID
    # // Encode does msgpack encoding of LocalRepo

    def  Encode(repo)->bytes :
        # var buf bytes.Buffer

        # encoder := codec.NewEncoder(&buf, &codec.MsgpackHandle{})
        # encoder.Encode(repo)
        pick=pickle.dumps(repo)
        return pick
    
    # // UpdateRefList changes package list for local repo
    def UpdateRefList(repo,reflist :PackageRefList)->None :
    	repo.packageRefs = reflist
    

# // NewLocalRepo creates new instance of Debian local repository
def NewLocalRepo(name , comment ) ->LocalRepo :
    localRepo=LocalRepo()
    localRepo.UUID=str(uuid.uuid4())
    localRepo.Name=name
    localRepo.Comment=comment
    return localRepo










# // Decode decodes msgpack representation into LocalRepo
# def (repo *LocalRepo) Decode(input []byte) error {
# 	decoder := codec.NewDecoderBytes(input, &codec.MsgpackHandle{})
# 	return decoder.Decode(repo)
# }
import pickle

# // LocalRepoCollection does listing, updating/adding/deleting of LocalRepos
class LocalRepoCollection :
    
    db   :database.Storage=None
    cache ={}
    # // ForEach runs method for each repository
    def  ForEach(collection,handler ) :
        def ForEachHandle(key,blob):
            r = pickle.loads(blob)
            # raise
            return handler(r)
        collection.db.ProcessByPrefix("L",ForEachHandle)
            # result.append(ForEachHandle(i.value,handler))
    
    
    # // Add appends new repo to collection and saves it
    def  Add(collection,repo :LocalRepo) :
        _,err=collection.ByName(repo.Name)

        if err is None:
            return (None,"local repo with name {} already exists".format(repo.Name))
        

        collection.Update(repo)
        # if err is not None  {
        # 	return err
        # }

        collection.cache[repo.UUID] = repo
        return (None,None)
    # // ByName looks up repository by name
    def ByName(collection,name :str) ->Tuple[LocalRepo,str] :
        def NoName(r:LocalRepo):
            return r.Name==name
        result = collection.search(NoName , True)
        if len(result) == 0 :
            returnStr="local repo with name {} not found".format( name)
            print('Error：',returnStr)
            return (None,returnStr)
        return (result[0],None)
        
    

    def search(collection,filter :bool, unique:bool)-> List[LocalRepo] :
        result :List[LocalRepo]= []
        for r in collection.cache :
            if filter(collection.cache[r]) :
                result.append(collection.cache[r])
            
        

        if unique and len(result) > 0 :
            return result
        def NoName(key,blob):
            r1 = LocalRepo()
            r1=pickle.loads(blob)
            if filter(r1) :
                exists = collection.cache.get(r1.UUID)
                if not exists :
                    collection.cache[r1.UUID] = r1
                    result.append(r1)
                    if unique :
                        return
                        # return errors.New("abort")
                    

            # return None
            return result

        data=collection.db.ProcessByPrefix("L",NoName)
        return result
            

    # // Update stores updated information about repo in DB
    def  Update(collection,repo :LocalRepo) :
        # transaction= collection.db.OpenTransaction()
        # if err is not None  {
        #     return err
        # }
        # defer transaction.Discard()

        collection.db.Put(repo.Key(), repo.Encode())
        # if err is not None  {
        #     return err
        # }
        if repo.packageRefs is not None  :
            pick=pickle.dumps(repo.packageRefs)
            collection.db.Put(repo.RefKey(), repo.packageRefs.Encode())
            # if err is not None  {
            #     return err
            # }
        
        # return transaction.Commit()
       
    # // LoadComplete loads additional information for local repo
    def LoadComplete(collection,repo :LocalRepo) :
        encoded= collection.db.Get(repo.RefKey())
        # if err == database.ErrNotFound {
        # 	return None
        # }
        # if err is not None  {
        # 	return err
        # }
        repo.packageRefs = PackageRefList()
        repo.packageRefs= repo.packageRefs.Decode(encoded)
        return None,None
    
    # // ByUUID looks up repository by uuid
    # def (collection *LocalRepoCollection) ByUUID(uuid string) (*LocalRepo, error) {
    # 	if r, ok := collection.cache[uuid]; ok {
    # 		return r, None
    # 	}

    # 	key := (&LocalRepo{UUID: uuid}).Key()

    # 	value, err := collection.db.Get(key)
    # 	if err == database.ErrNotFound {
    # 		return None, fmt.Errorf("local repo with uuid %s not found", uuid)
    # 	}

    # 	if err is not None  {
    # 		return None, err
    # 	}

    # 	r := &LocalRepo{}
    # 	err = r.Decode(value)

    # 	if err is None  {
    # 		collection.cache[r.UUID] = r
    # 	}

    # 	return r, err
    # }



    # // Len returns number of remote repos
    # def (collection *LocalRepoCollection) Len() int {
    # 	return len(collection.db.KeysByPrefix([]byte("L")))
    # }

    # // Drop removes remote repo from collection
    # def (collection *LocalRepoCollection) Drop(repo *LocalRepo) error {
    # 	transaction, err := collection.db.OpenTransaction()
    # 	if err is not None  {
    # 		return err
    # 	}
    # 	defer transaction.Discard()

    # 	delete(collection.cache, repo.UUID)

    # 	if _, err = transaction.Get(repo.Key()); err is not None  {
    # 		if err == database.ErrNotFound {
    # 			return errors.New("local repo not found")
    # 		}
    # 		return err
    # 	}

    # 	if err = transaction.Delete(repo.Key()); err is not None  {
    # 		return err
    # 	}

    # 	if err = transaction.Delete(repo.RefKey()); err is not None  {
    # 		return err
    # 	}

    # 	return transaction.Commit()
    # }

    
# // NewLocalRepoCollection loads LocalRepos from DB and makes up collection
def NewLocalRepoCollection(db :database.Storage)->LocalRepoCollection:
    localRepoCollection=LocalRepoCollection()
    
    localRepoCollection.db=db
    return localRepoCollection
# }







